package com.link.base.core.basic.config.mvc;

import com.alibaba.fastjson.JSONObject;
import com.link.core.basic.annotations.JsonParam;
import com.link.core.basic.model.BasicModel;
import com.link.core.util.ReflectUtil;
import com.link.core.util.StringUtils;
import com.link.base.core.basic.util.YmlConstants;
import org.apache.commons.io.IOUtils;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 处理controller接收多个json参数对象或者普通form参数
 *
 * @author 李杨
 * @date 2018/07/13
 * @updated 2018/08/14
 */
public class JsonParamArgumentResolver implements HandlerMethodArgumentResolver {

    /**
     * 接收请求的参数key
     */
    private static final String JSON_REQUEST_BODY = "JSON_REQUEST_BODY";

    /**
     * 判断参数是否为json格式
     */
    private static final Pattern PATTERN = Pattern.compile("(application/json)", Pattern.CASE_INSENSITIVE);

    /**
     * 指定字段更新过滤掉不必要的字段
     */
    private static final String[] noUpdateFields = {"id", "loginCorpId", "rowVersion", "row_status",
        "totalFlag", "selectedFlag", "loginType", "login", "basicOrgId", "basicPostnId", "total",
        "onlyCountFlag", "startRow", "created", "totalPage", "pageFlag", "endRow", "rows", "privateAddr", "createdBy", "page",
        "corpid", "lastUpdated", "lastUpdatedBy", "updateFields"};

    /**
     * 处理@JsonParam修饰的参数
     */
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.hasParameterAnnotation(JsonParam.class);
    }

    /**
     * 具体处理请求参数的方法，目前只是处理json和form两种传参方式
     * @author 李杨
     * @date 2018/08/14
     */
    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
                                  NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
        //获取Controller中的参数名
        String ctrlParamName = parameter.getParameterName();
        //获取Controller中的参数类型
        Class ctrlParamTypeClass = parameter.getParameterType();
        //实例化参数对象
        Object targetObj = ctrlParamTypeClass.newInstance();

        //有些前端会给contentType添加一些参数，例如：application/json;charset=UTF-8，添加一个jsonFlag用于判断是否为json格式
        boolean jsonFlag = false;
        if (!StringUtils.isBlank(servletRequest.getContentType())) {
            Matcher match = PATTERN.matcher(servletRequest.getContentType());
            while (match.find()) {
                jsonFlag = true;
            }
        }
        //判断contentType类型，处理json
        if (jsonFlag) {
            String requestBodyStr = getRequestBody(servletRequest);
            //如果请求体位空，直接返回
            if (StringUtils.isBlank(requestBodyStr)) {
                return targetObj;
            }
            //声明参数值
            Object fieldValue = null;
            //获取参数所有属性
            List<Field> allFields = ReflectUtil.getAllFields(ctrlParamTypeClass);
            JSONObject jsonParam = JSONObject.parseObject(requestBodyStr);
            if (jsonParam.size() > 0) {
                //创建WebDataBinder对象 反射 遍历fields给属性赋值
                WebDataBinder binder = binderFactory.createBinder(webRequest, null, ctrlParamName);
                //声明参数的key
                Set<String> keySet = jsonParam.keySet();
                Iterator<String> iter = keySet.iterator();
                //循环参数并赋值
                loopJsonKeySet(iter, allFields, jsonParam, binder, parameter, targetObj);
                //处理updateFields
            }
            return targetObj;
            //处理非json格式的参数，但是这里没有进一步划分，默认处理form表单格式的参数，使用其它方式传值还未验证
        } else {
            //声明参数值
            Object fieldValue = null;
            //获取参数所有属性
            List<Field> allFields = ReflectUtil.getAllFields(ctrlParamTypeClass);
            WebDataBinder binder = binderFactory.createBinder(webRequest, null, ctrlParamName);
            //获取所有的请求参数名
            Iterator<String> iter = webRequest.getParameterNames();
            //循环参数并赋值
            loopFormKeySet(iter, allFields, webRequest, binder, parameter, targetObj);
            //处理updateFields
            return targetObj;
        }

    }

    /**
     * 循环json参数
     */
    private void loopJsonKeySet(Iterator<String> iter, List<Field> allFields, JSONObject jsonParam,
                                WebDataBinder binder, MethodParameter parameter, Object targetObj) throws Exception {
        while (iter.hasNext()) {
            String key = iter.next();
            for (Field field : allFields) {
                String fieldName = field.getName();
                if (key.equals(fieldName)) {
                    //声明参数的值（实际参数）
                    Object arg;
                    field.setAccessible(true);
                    Class<?> fieldType = field.getType();
                    //如果参数类型为List
                    if (fieldType == List.class) {
                        Type genericType = field.getGenericType();
                        arg = new Object();
                        //如果是泛型参数
                        if (genericType instanceof ParameterizedType) {
                            ParameterizedType parameterizedType = (ParameterizedType) genericType;
                            //得到泛型类
                            Class<?> genericClass;
                            if (parameterizedType.getActualTypeArguments()[0].getTypeName().indexOf("java.util.Map") >= 0) {
                                //如果泛型的类型为Map，则在实例化Map时，去掉Map自身的泛型（这里有待进一步完善判断，需判断所有带有泛型的类型）
                                genericClass = Class.forName("java.util.Map");
                            } else {
                                genericClass = (Class<?>) parameterizedType.getActualTypeArguments()[0];
                            }
//                            Class<?> genericClass = (Class<?>) parameterizedType.getActualTypeArguments()[0];
                            //生成一个list
                            String paramValue = jsonParam.getString(key);
                            if (StringUtils.isBlank(paramValue)) {
                                paramValue = "[]";
                            }
                            List<?> listArg = JSONObject.parseArray(paramValue, genericClass);
                            //生成一个类型为list的绑定参数
                            arg = binder.convertIfNecessary(listArg, fieldType, parameter);
                        }
                        //如果参数类型是BasicModel的子类
                    } else if (BasicModel.class.isAssignableFrom(fieldType)) {
                        //生成一个父类为BasicModel的实体类
                        Object obj = JSONObject.parseObject(jsonParam.getString(key), fieldType);
                        //生成一个类型为自定义Model的绑定参数
                        arg = binder.convertIfNecessary(obj, fieldType, parameter);
                    } else {
                        arg = binder.convertIfNecessary(jsonParam.getString(key), fieldType, parameter);
                    }
                    //给成员变量赋值
                    field.set(targetObj, arg);
                }
            }
        }
    }

    /**
     * 循环表单参数
     */
    private void loopFormKeySet(Iterator<String> iter, List<Field> allFields, NativeWebRequest webRequest,
                                WebDataBinder binder, MethodParameter parameter, Object targetObj) throws Exception {
        while (iter.hasNext()) {
            //声明参数的key
            String paramKey = iter.next();
            //获取参数的值（传入参数）
            String paramValue = webRequest.getParameter(paramKey);
            for (Field field : allFields) {
                String fieldName = field.getName();
                if (paramKey.equals(fieldName)) {
                    //声明参数的值（实际参数）
                    Object arg;
                    field.setAccessible(true);
                    Class<?> fieldType = field.getType();
                    //如果参数类型为List
                    if (fieldType == List.class) {
                        Type genericType = field.getGenericType();
                        arg = new Object();
                        //如果是泛型参数
                        if (genericType instanceof ParameterizedType) {
                            ParameterizedType parameterizedType = (ParameterizedType) genericType;
                            //得到泛型类
                            Class<?> genericClass;
                            if (parameterizedType.getActualTypeArguments()[0].getTypeName().indexOf("java.util.Map") >= 0) {
                                //如果泛型的类型为Map，则在实例化Map时，去掉Map自身的泛型（这里有待进一步完善判断，需判断所有带有泛型的类型）
                                genericClass = Class.forName("java.util.Map");
                            } else {
                                genericClass = (Class<?>) parameterizedType.getActualTypeArguments()[0];
                            }
//                            Class<?> genericClass = (Class<?>) parameterizedType.getActualTypeArguments()[0];
                            //生成一个list
                            if (StringUtils.isBlank(paramValue)) {
                                paramValue = "[]";
                            }
                            List<?> listArg = JSONObject.parseArray(paramValue, genericClass);
                            //生成一个类型为list的绑定参数
                            arg = binder.convertIfNecessary(listArg, fieldType, parameter);
                        }
                        //如果参数类型是BasicModel的子类
                    } else if (BasicModel.class.isAssignableFrom(fieldType)) {
                        //生成一个父类为BasicModel的实体类
                        Object obj = JSONObject.parseObject(paramValue, fieldType);
                        //生成一个类型为自定义Model的绑定参数
                        arg = binder.convertIfNecessary(obj, fieldType, parameter);
                    } else {
                        arg = binder.convertIfNecessary(paramValue, fieldType, parameter);
                    }
                    //给成员变量赋值
                    field.set(targetObj, arg);
                }
            }
        }
    }

    /**
     * 得到请求体的值
     */
    private String getRequestBody(HttpServletRequest servletRequest) {
        String jsonBody = (String) servletRequest.getAttribute(JSON_REQUEST_BODY);
        if (jsonBody == null) {
            try {
                jsonBody = IOUtils.toString(servletRequest.getInputStream(), "UTF-8");
                servletRequest.setAttribute(JSON_REQUEST_BODY, jsonBody);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        return jsonBody;
    }

    /**
     * 处理前端传入的更新字段
     */
    private void processUpdateFileds(Iterator<String> iter, BasicModel basicModel) {
        //当指定字段更新的开关为"Y"，并且updateFields没有传入值时，才会将前端传入的字段全部转换为updateFields的值
        if ("Y".equalsIgnoreCase(YmlConstants.getUpdateSpecifiedFields()) && StringUtils.isBlank(basicModel.getUpdateFields())) {
            StringBuilder updateFields = new StringBuilder("");
//        List<String> updateFields = new ArrayList<>(16);
            String field;
            boolean matchFlag;
            while (iter.hasNext()) {
                matchFlag = false;
                field = iter.next();
                innerLoop : for (int i = 0; i < noUpdateFields.length; i++) {
                    if (noUpdateFields[i].equalsIgnoreCase(field)) {
                        matchFlag = true;
                        break innerLoop;
                    }
                }
                if (!matchFlag) {
                    updateFields.append(field);
                    updateFields.append(",");
                }
            }
            if (!"".equals(updateFields.toString()) || updateFields.toString().length() > 0) {
                basicModel.setUpdateFields(updateFields.toString().substring(0, updateFields.length() - 1));
            }
        }
    }
}
